/**
 * com.chaeron.GPS - An API for access to GPS devices.
 *
 *
 * Copyright (c) 2001, 2002 Chaeron Corporation,
 * All rights reserved.
 * 
 * Chaeron grants you a non-exclusive license to use, modify and re-distribute
 * this program under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version, provided that this copyright notice and 
 * license appear on all copies of the software.
 * 
 * Software is provided "AS IS," without a warranty of any kind. ALL EXPRESS OR
 * IMPLIED REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE
 * HEREBY EXCLUDED. THE ENTIRE RISK ARISING OUT OF USING THE SOFTWARE IS ASSUMED
 * BY THE LICENSEE. See the GNU General Public License for more details.
 *
 * CHAERON AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY
 * LICENSEE OR ANY THIRD PARTY AS A RESULT OF USING OR DISTRIBUTING SOFTWARE.
 * IN NO EVENT WILL CHAERON OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE,
 * PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL
 * OR PUNITIVE DAMAGES, HOWEVER CAUSED AN REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF CHAERON HAS
 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * @version     1.02
 * @author  	Andrzej Jan Taramina, Chaeron Corporation
 *  			andrzej@chaeron.com
 */

package com.chaeron.GPS;

//***************************************************************************
//*
//* 	Imports
//*
//***************************************************************************/

import com.chaeron.GPS.event.*;
import com.chaeron.GPS.event.decorator.*;
import com.chaeron.GPS.exception.*;
import com.chaeron.GPS.NMEA.*;
import com.chaeron.GPS.Garmin.*;
import com.chaeron.platform.common.*;


//***************************************************************************
//*
//* 	GPS Class definition
//*
//***************************************************************************/

/**
 * The GPS class is an abstract class that provides factory methods to create
 * protocol-specific subclass instances.  The GPS class public methods are used
 * to interface to the GPS device in a protocol-independent manner.
 *
 */

public abstract class GPS
{
	
	//***************************************************************************
	//*
	//* 	Constant definitions
	//*
	//***************************************************************************/
	
	
	// Version information
	
	private final static int		version								= 102;
	
	
	// Protocol definitions
	
	/**
	 * The constant for NMEA Protocol.
	 */
	public final static int 		PROTOCOL_NMEA   					= 1;
	
	/**
	 * The constant for NMEA Protocol.
	 */
	public final static int 		PROTOCOL_GARMIN 					= 2;
	
	
	// Other internal constants
	
	private final static int 	 	DEFAULT_PORT		  				= 0;
	private final static int		DEFAULT_BAUD_RATE  				 	= 4800;
	private final static int		DEFAULT_SERIALBUFFER_LEN   		 	= 270;
	public final static int			DEFAULT_PACKETBUFFER_LEN   		 	= 270;
	private final static int		DEFAULT_SERIAL_ALTERNATE_READLEN	= DEFAULT_SERIALBUFFER_LEN;
	private final static int		SERIAL_PORT_ALTERNATE_INPUT   		= -1;
	private final static int		SERIAL_STATE_STOPPED   		   		= 0;
	private final static int		SERIAL_STATE_PACKET_BEGIN  	  		= 1;
	private final static int		SERIAL_STATE_PACKET_END		    	= 2;
	private final static int		SERIAL_STATE_PACKET_DOUBLE    		= 3;
	
	private final static int		DEFAULT_EVENT_QUEUE_SIZE			= 10;
	
	// Position Fix Type Labels
	private final String[]	positionFixLabels							=
	{
		"Unuseable",
		"Invalid",
		"2D",
		"3D",
		"2D Differential",
		"3D Differential"
	};
	
	
	// Various Conversion factors
	
	protected final static double 	PI 									= 3.14159265358979323846d;
	protected final static double	DEGREES_TO_RADIANS					= PI / 180.0d;
	protected final static double	RADIANS_TO_DEGREES					= 180.0d / PI;
	protected final static double	KNOTS_TO_METERSPERSECOND			= 0.514444444d; 
	protected final static double	KNOTS_TO_MPH						= 1.15077944802d; 
	protected final static double	KNOTS_TO_KPH						= 1.852d; 
	protected final static double	METERSPERSECOND_TO_KNOTS			= 1.94384449244d; 
	protected final static double	METERSPERSECOND_TO_MPH				= 2.23693629205d; 
	protected final static double	METERSPERSECOND_TO_KPH				= 3.6d;
	protected final static double	METRES_TO_FEET						= 3.28083989501d;
	protected final static double	FEET_TO_METRES						= 0.3048d;
	
	public final static float		UNKNOWN_VERTICAL_MEASURE			= 1.0E25f;
	
	private final static char[] HEX_CHARS = {	'0','1','2','3','4','5','6','7','8','9', 'A','B','C','D','E','F'};
	
	
	//***************************************************************************
	//*
	//* 	Attributes
	//*
	//***************************************************************************/							
	
	public	Display					display								= null;
	
	// Serial Port communication attributes
	
	private boolean 				connected   						= false;
	private SerialPort				serialPort							= null;
	private int					 	serialPortNumber   				 	= DEFAULT_PORT;
	private int 					serialBaudRate  					= DEFAULT_BAUD_RATE;
	private int 					serialReadTimeout   				= SerialPort.DEFAULT_READ_TIMEOUT;
	private boolean 				serialCheckRead   					= true;
	
	// Serial Byte Array attributes (used to simulate Serial Port reads from a byte array)
	
	private byte[]					serialByteArray						= null;
	private int						serialByteArraySize					= -1;
	private int						serialByteArrayOffset				= 0;
	private boolean					serialAlternateOpen					= false;
	private int						serialAlternateMaxReadLen			= DEFAULT_SERIAL_ALTERNATE_READLEN;
	
	// Serial Catalog attributes (used to simulate Serial Port reads from a Catalog)
	
	private Catalog					serialCatalog						= null;
	private int						serialCatalogRecord					= 0;
	private int						serialCatalogRecordBytesLeft		= 0;
	private boolean					serialCatalogEOF					= false;
	
	private int						serialState							= SERIAL_STATE_STOPPED;
	private byte[]					serialReadBuffer					= null;
	private int						serialReadBufferSize				= DEFAULT_SERIALBUFFER_LEN * 4;
	private int						serialReadBufferStart				= 0;
	private byte[]					serialWriteBuffer					= new byte[ DEFAULT_SERIALBUFFER_LEN ];
	private int						serialWriteBufferSize				= DEFAULT_SERIALBUFFER_LEN;
	
	private byte[]					packetBuffer						= null;
	private int						packetBufferSize					= DEFAULT_PACKETBUFFER_LEN;
	private int						packetBufferPos;
	
	
	// Packet Queue
	
	private Vector					packetQueue							= null;
	
	
	// GPSEvent Listener attributes
	
	private Vector					eventQueue							= null;
	private Vector					listeners							= new Vector( 5 );
	private int						listenerEvents						= 0;
	private boolean					blockUnfilledPackets				= true;
	
	
	// Last Position, Velocity, Time received
	
	private GPSPosition				lastPosition						= null;
	private GPSVelocity				lastVelocity						= null;
	private GPSTime					lastTime							= null;
	
	
	//***************************************************************************
	//*
	//* 	Constructors
	//*
	//***************************************************************************/
	
	/**
	 * Constructor used for initialization of various attributes
	 */
	
	public GPS()
	{
		if( Compile.PACKET_QUEUE_SIZE > 0 ) {
			packetQueue = new Vector( Compile.PACKET_QUEUE_SIZE );
		} else if( Compile.PACKET_QUEUE_SIZE < 0 ) {
			packetQueue = new Vector( -Compile.PACKET_QUEUE_SIZE );
		}
		
		if( Compile.EVENT_QUEUE_SIZE > 0 ) {
			eventQueue = new Vector( Compile.EVENT_QUEUE_SIZE );
		} else if( Compile.EVENT_QUEUE_SIZE < 0 ) {
			eventQueue = new Vector( -Compile.EVENT_QUEUE_SIZE );
		} else {
			eventQueue = new Vector( DEFAULT_EVENT_QUEUE_SIZE );
		}
	}
	
	
	//***************************************************************************
	//*
	//* 	Static Methods
	//*
	//***************************************************************************/
	
	/**
	 * Static factory method that returns a concrete instance of GPS 
	 *
	 * @param	protocol	Value denoting the protocol to be used
	 * @return   			concrete GPS instance
	 * @throws   			GPSProtocolException if an invalid protocol is specified
	 */
	
	public static GPS newInstance( int protocol ) throws GPSProtocolException
	{
		GPS     instance    = null;
		
		switch( protocol ) {
			case PROTOCOL_NMEA:
				if( Compile.INCLUDE_PROTOCOL_NMEA ) {
					instance = new NMEAGPSImpl();
				} else {
					throw( new GPSProtocolException() );
				}
				break;
				
			case PROTOCOL_GARMIN:
				if( Compile.INCLUDE_PROTOCOL_GARMIN ) {
					instance = new GarminGPSImpl();
				} else {
					throw( new GPSProtocolException() );
				}
				break;
				
			default:
				throw( new GPSProtocolException() );
		}
		
		return( instance );
	}
	
	
	//***************************************************************************
	//*
	//* 	Static Conversion Methods
	//*
	//***************************************************************************/
	
	/**
	 * Convert Meters/Sec to Knots 
	 *
	 * @param	mps		velocity in meters/sec
	 * @return				velocity in knots
	 */
	
	public static double convertMetersSecToKnots( double mps )
	{  
		return( mps * METERSPERSECOND_TO_KNOTS );
	}
	
	
	/**
	 * Convert Knots to Meters/Sec
	 *
	 * @param	knots		velocity in knots
	 * @return				velocity in meters/sec
	 */
	
	public static double convertKnotsToMetersSec( double knots )
	{  
		return( knots * KNOTS_TO_METERSPERSECOND );
	}
	
	
	/**
	 * Convert Knots to KPH
	 *
	 * @param	knots		velocity in knots
	 * @return				velocity in KPH
	 */
	
	public static double convertKnotsToKPH( double knots )
	{  
		return( knots * KNOTS_TO_KPH );
	}
	
	
	/**
	 * Convert to Meters/Sec to KPH
	 *
	 * @param	mps		velocity in Meters/Sec
	 * @return				velocity in KPH
	 */
	
	public static double convertMetersSecToKPH( double mps )
	{  
		return( mps * METERSPERSECOND_TO_KPH );
	}
	
	
	/**
	 * Convert Radians to Degrees
	 *
	 * @param	radians		radians
	 * @return				degrees
	 */
	
	public static double convertRadiansToDegrees( double radians )
	{  
		return( radians * RADIANS_TO_DEGREES );
	}
	
	
	/**
	 * Convert Metres to Feet
	 *
	 * @param	degrees		metres
	 * @return				feet
	 */
	
	public static double convertMetresToFeet( double metres )
	{  
		return( metres * METRES_TO_FEET );
	}
	
	
	/**
	 * Convert Feet to Metres
	 *
	 * @param	radians		feet
	 * @return				metres
	 */
	
	public static double convertFeetToMetres( double feet )
	{  
		return( feet * FEET_TO_METRES );
	}
	
	
	/**
	 * Convert Degrees to Radians
	 *
	 * @param	degrees		degrees
	 * @return				radians
	 */
	
	public static double convertDegreesToRadians( double degrees )
	{  
		return( degrees * DEGREES_TO_RADIANS );
	}
	
	//***************************************************************************
	//*
	//* 	XML Formatting methods for GPSValue vectors
	//*
	//***************************************************************************/

	
	/**
	 * Start XML Output.  If you are going to write multiple GPSValue objects out, then
	 * you should do a startXMLOutput first, do all the writes and end with an endXMLOutput
	 * if you want the XML written to stay well formed. Basically, it will wrap the generated XML
	 * in a <GPSData> </GPSData> pair of tags.
	 *
	 * @param	declaration		include XML Declaration?
	 * @param	namespace		include namspace specification?
	 * @return	xml string
	 * @see 	#endXMLOutput()
	 */
	
	public String startXMLOutput( boolean declaration, boolean namespace )
	{  
		StringBuffer		xml = new StringBuffer();
		
		if( Compile.INCLUDE_EVENT_TOXML ) {
			if( declaration ) {
				xml.append( GPSXMLTag.XML_DECLARATION );
			}
			xml.append( GPSEventDecorator.genXMLTag( GPSXMLTag.XML_TAG_GPSDATA, GPSXMLTag.XML_TAG_GPSDATA_SCHEMAS, null, true, false, namespace ) );
		}
		
		return( xml.toString() );
	}
	
	
	/**
	 * End XML Output.  This generates a /GPSData XML Tag to encapsulate other collections
	 *
	 * @param	declaration		include XML Declaration?
	 * @return	xml string
	 */
	
	public String endXMLOutput()
	{  
		StringBuffer		xml = new StringBuffer();
		
		if( Compile.INCLUDE_EVENT_TOXML ) {
			xml.append( GPSEventDecorator.genXMLEndTag( GPSXMLTag.XML_TAG_GPSDATA ) );
		}
		
		return( xml.toString() );
	}
		
	/**
	 * Output a vector of GPSValues as XML
	 *
	 * @param	vector			vector of GPSValues
	 * @param	declaration		include XML Declaration?
	 * @param	namespace		include namspace specification?
	 * @return	xml string
	 */
	
	public String gpsValuesVectorToXML( Vector vector, boolean declaration, boolean namespace )
	{  
		StringBuffer		xml = new StringBuffer();
		Object				gpsValue;
		
		if( Compile.INCLUDE_EVENT_TOXML ) {
			if( declaration ) {
				xml.append( GPSXMLTag.XML_DECLARATION );
			}
			xml.append( GPSEventDecorator.genXMLStartTag( GPSXMLTag.XML_TAG_COLLECTION, namespace ) );
			
			for( int i = 0; i < vector.getCount(); i++ ) {
				gpsValue = vector.get( i );
				if( gpsValue instanceof GPSValue ) {
					xml.append( ((GPSValue)gpsValue).toXML( false, false ) );
				} else {
					xml.append( GPSEventDecorator.genXMLValueTag( GPSXMLTag.XML_TAG_UNKNOWN, gpsValue.toString() ) );
				}
				
			}
			
			xml.append( GPSEventDecorator.genXMLEndTag( GPSXMLTag.XML_TAG_COLLECTION ) );
		}
		
		return( xml.toString() );
	}
	
	
	/**
	 * Start XML Output to Catalog.  If you are going to write multiple GPSValue objects out, then
	 * you should do a startXMLOutput first, do all the writes and end with an endXMLOutput
	 * if you want the XML written to stay well formed.  Basically, it will wrap the generated XML
	 * in a <GPSData> </GPSData> pair of tags.
	 *
	 * @param	declaration		include XML Declaration?
	 * @param	namespace		include namspace specification?
	 * @param	catalog			catalog to be written to.
	 * @see 	#endXMLOutput( Catalog catalog )
	 */
	
	public void startXMLOutput( Catalog catalog, boolean declaration, boolean namespace )
	{  	
		String 				tag;
		
		if( Compile.INCLUDE_EVENT_TOXML && Compile.INCLUDE_EVENT_SAVETOCATALOG  ) {
			if( declaration ) {
				tag = GPSXMLTag.XML_DECLARATION;
				GPSEventDecorator.writeString( catalog, tag );
			}
			tag = GPSEventDecorator.genXMLTag( GPSXMLTag.XML_TAG_GPSDATA, GPSXMLTag.XML_TAG_GPSDATA_SCHEMAS, null, true, false, namespace );
			GPSEventDecorator.writeString( catalog, tag );
		}
	}
	
	/**
	 * End XML Output to Catalog.  This generates a /GPSData XML Tag to encapsulate other collections
	 *
	 * @param	declaration		include XML Declaration?
	 * @param	catalog			catalog to be written to.
	 */
	
	public void endXMLOutput( Catalog catalog )
	{  
		String				tag;
		
		if( Compile.INCLUDE_EVENT_TOXML && Compile.INCLUDE_EVENT_SAVETOCATALOG  ) {
			tag = GPSEventDecorator.genXMLEndTag( GPSXMLTag.XML_TAG_GPSDATA );
			GPSEventDecorator.writeString( catalog, tag );
		}
	}
	
	/**
	 * Output a vector of GPSValues as XML to the given catalog
	 *
	 * @param	catalog			output catalog
	 * @param	vector			vector of GPSValues
	 * @param	declaration		include XML Declaration?
	 * @param	namespace		include namspace specification?
	 */
	
	public void gpsValuesVectorToXML( Catalog catalog, Vector vector, boolean declaration, boolean namespace )
	{  
		Object				gpsValue;
		String				tag;
		
		if( Compile.INCLUDE_EVENT_TOXML && Compile.INCLUDE_EVENT_SAVETOCATALOG  ) {
			if( declaration ) {
				tag = GPSXMLTag.XML_DECLARATION;
				GPSEventDecorator.writeString( catalog, tag );
			}
			tag = GPSEventDecorator.genXMLStartTag( GPSXMLTag.XML_TAG_COLLECTION, namespace );
			GPSEventDecorator.writeString( catalog, tag );
			
			for( int i = 0; i < vector.getCount(); i++ ) {
				gpsValue = vector.get( i );
				if( gpsValue instanceof GPSValue ) {
					((GPSValue)gpsValue).toXML( catalog, false, false );
				} else {
					tag = GPSEventDecorator.genXMLValueTag( GPSXMLTag.XML_TAG_UNKNOWN, gpsValue.toString() );
					GPSEventDecorator.writeString( catalog, tag );
				}
				
			}
			
			tag = GPSEventDecorator.genXMLEndTag( GPSXMLTag.XML_TAG_COLLECTION );
			GPSEventDecorator.writeString( catalog, tag );
		}
	}
	
	
	//***************************************************************************
	//*
	//*    Misc Accessor Methods
	//*
	//***************************************************************************/
	
	/**
	 * Get the Version number for this implementation.  The version is returned as
	 * an integer with two decimal places assumed.  So a value of 101 means version 
	 * 1.01.  The value will be made negative for beta versions!
	 *
	 * @return   			The version
	 */
	
	public int getVersion() 
	{
		return( version );
	}
	
	
	/**
	 * Get the Version number for this implementation as a String.  
	 *
	 * @return   			The version
	 */
	
	public String getVersionStr() 
	{
		String		ver;
		int			absver;
		int			major;
		int			minor;
		
		absver	= version;
		if( absver < 0 ) {
			absver = -absver;
		}
		
		major 	= absver / 100;
		minor 	= absver % 100;
		
		ver 	=  String.valueOf( major );
		ver 	+= ".";
		
		if( minor < 10 ) {
			ver += "0";
		}
		
		ver 	+= String.valueOf( minor );
		
		if( version <= 0 ) {
			ver += " Beta";
		}
		
		return( ver );
	}
	
	
	/**
	 * Get the text label for a particular Position Fix Type
	 *
	 * @param	fix   	the Position fix type
	 * @return   			Fix type label
	 */
	
	public String getPositionFixLabel( int fix ) 
	{
		String		label;
		
		label = "Invalid Position Fix Type";
		
		if( fix >= 0 && fix < positionFixLabels.length ) {
			label = positionFixLabels[ fix ];
		}
		
		return( label );
	}
	
	
	/**
	 * Get the last Position we received.  Null is returned if there is no saved Position
	 *
	 * @return   			The last Position received.  Null if none received yet.
	 */
	
	public GPSPosition getLastPosition() 
	{
		return( (GPSPosition)parse( (Parseable)lastPosition ) );
	}
	
	
	/**
	 * Get the last Velocity we received.  Null is returned if there is no saved Velocity
	 *
	 * @return   			The last Velocity received.  Null if none received yet.
	 */
	
	public GPSVelocity getLastVelocity() 
	{
		return( (GPSVelocity)parse( (Parseable)lastVelocity ) );
	}
	
	
	/**
	 * Get the last Time we received.  Null is returned if there is no saved Time
	 *
	 * @return   			The last Time received.  Null if none received yet.
	 */
	
	public GPSTime getLastTime() 
	{
		return( (GPSTime)parse( (Parseable)lastTime ) );
	}
	
	
	/**
	 * Parse a saved event
	 *
	 * @return   			The event or null if there was a parsing error
	 */
	
	private Parseable parse( Parseable parseable ) 
	{
		Parseable		ret				= parseable;
		
		if( parseable != null ) {
			try {
				parseable.parse();
			}
			catch( GPSException exception ) {
				postGPSEvent( new GPSEvent( GPSEvent.GPS_EVENT_ERROR, parseable.toString(), exception ) );
				ret = null;
			}
		}
		
		return( ret );
	}
	
	
	//***************************************************************************
	//*
	//*    Comm Base Class Methods
	//*
	//***************************************************************************/
	
	/**
	 * Are we connected?
	 *
	 * @return   	connected, true/false
	 */
	
	public final boolean isConnected()
	{
		return( connected );
	}
	
	
	/**
	 * Connect the GPS.  
	 *
	 * @throws GPSConnectionException if the connection is already open
	 * @throws GPSSerialPortException
	 */
	
	public final void connect() throws GPSConnectionException, GPSSerialPortException, GPSOperationUnavailableException
	{
		if( !connected ) {
			connectingNotify();
			openSerial();
			connected = true;
			connectedNotify();
		} else {
			throw( new GPSConnectionException( GPSConnectionException.EXCEPTION_CONNECTED ) );
		}
	}
	
	
	/**
	 * Disconnect the GPS.  
	 * @throws GPSConnectionException if the connection is not open
	 * @throws GPSSerialPortException
	 */
	
	public final void disconnect() throws GPSConnectionException, GPSSerialPortException
	{
		if( connected ) {
			connected = false;
			disconnectingNotify();
			closeSerial();
			disconnectedNotify();
		} else {
			throw( new GPSConnectionException( GPSConnectionException.EXCEPTION_NOTCONNECTED ) );
		}
	}
	
	
	/**
	 * onPacket will be called when a complete packet has been read from the serial port
	 *
	 * @param		packet		byte array for packet
	 * @param		len			number of bytes in packet
	 */
	
	private  void onPacket( byte[] packet, int len ) 
	{
		if( (listenerEvents & GPSEvent.GPS_EVENT_RAW_DATA) > 0 ) {
			postGPSEvent( new GPSEvent( GPSEvent.GPS_EVENT_RAW_DATA, packet, len  ) );
		}
		
		if( Compile.PACKET_QUEUE_SIZE == 0 ) {
			onPacketNotify( packet, len );
		} else {
			queuePacket( packet, len );
		}
	}	
	
	/**
	 * Queue a GPS packet
	 *
	 * @param		packet		byte array for packet
	 * @param		len			number of bytes in packet
	 */
	
	private  void queuePacket( byte[] packet, int len ) 
	{
		byte[]	buffer		= new byte[ len ];
		int		i;
		
		// Duplicate the packet.
		
		for( i = 0; i < len; i++ ) {
			buffer[ i ] = packet[ i ];
		}
		
		synchronized( packetQueue ) {
			if( Compile.PACKET_QUEUE_SIZE > 0 && packetQueue.getCount() >= Compile.PACKET_QUEUE_SIZE ) {
				packetQueue.del( 0 );
			} 
			
			packetQueue.add( buffer );
		}
	}	
	
	
	//***************************************************************************
	//*
	//*    Waypoint Class Methods
	//*
	//***************************************************************************/
	
	/**
	 * Download Waypoints from the GPS unit.  The
	 * GPS_EVENT_WAYPOINT_TRANSFER_COMPLETE event will be generated, with a Vector
	 * of waypoints as the event object, when the download is done.  If an error occured
	 * the event object will be a GPSException with details of what caused the problem. You should not
	 * normally have event listeners registered for the more granular waypoint events if you
	 * use this method (ie. GPS_EVENT_RECORDS, GPS_EVENT_WAYPOINT and GPS_EVENT_XFER_COMPLETE), as this
	 * might interfere with the download processing.
	 *
	 * @throws GPSException if the connection is not open
	 * @see #startDownloadWaypoints()
	 */
	
	public final void downloadWaypoints() throws GPSException
	{
		if( !connected ) {
			throw( new GPSConnectionException( GPSConnectionException.EXCEPTION_NOTCONNECTED ) );
		} else {
			downloadWaypointsImpl();
		}
	}
	
	
	/**
	 * <p>Start Downloading Waypoints from the GPS unit. This is used if you want to handle
	 * your own event management rather than use the downloadWaypoints() call.</p>
	 *
	 * <p>You will receive a GPS_EVENT_RECORDS event first which tells you how many Waypoints will be downloaded.
	 * You will then receive a number GPS_EVENT_WAYPOINT events (base on what you got in the GPS_EVENT_RECORDS
	 * event), and finally a GPS_EVENT_XFER_COMPLETE event when all waypoints have been sent to you by the GPS.</p>
	 *
	 *<p> NOTE:  If you had an interrupted transfer earlier, you may receive GPS_EVENT_WAYPOINT events prior to
	 * getting a GPS_EVENT_RECORDS, and the number of waypoints received may not match the count in GPS_EVENT_RECORDS when
	 * you finally receive the GPS_EVENT_XFER_COMPLETE event.  You need to be able to handle this situation</p>
	 *
	 * <p>Basically, you need to implement a GPSListener onGPSEvent() method that listens for these events
	 * and take appropriate actions as you receive the events. </p> 
	 *
	 * <p>For an example of how this is done, you can look at the implementation of the downloadWaypoints()
	 * call in the GarminGPSImpl.java source code, which manages these events under the covers.</p>
	 *
	 * @throws GPSConnectionException if the connection is not open
	 * @see #downloadWaypoints()
	 */
	
	public final void startDownloadWaypoints() throws GPSException
	{
		if( !connected ) {
			throw( new GPSConnectionException( GPSConnectionException.EXCEPTION_NOTCONNECTED ) );
		} else {
			startDownloadWaypointsImpl();
		}
	}
	
	
	/**
	 * Upload an array of Waypoints to the GPS unit.  
	 *
	 * @param	waypoints   An array of waypoints
	 * @throws GPSConnectionException if the connection is not open
	 */
	
	public final void uploadWaypoints( GPSWaypoint[] waypoints ) throws GPSException
	{
		if( !connected ) {
			throw( new GPSConnectionException( GPSConnectionException.EXCEPTION_NOTCONNECTED ) );
		} else {
			uploadWaypointsImpl( waypoints );
		}
	}
	
	
	//***************************************************************************
	//*
	//*    Route Class Methods
	//*
	//***************************************************************************/
	
	/**
	 * Download Routes from the GPS unit.  The
	 * GPS_EVENT_ROUTE_TRANSFER_COMPLETE event will be generated, with a vector
	 * of routes as the event object, when the download is done.  If an error occured
	 * the event object will be a GPSException with details of what caused the problem. You should not
	 * normally have event listeners registered for the more granular waypoint events if you
	 * use this method (ie. GPS_EVENT_RECORDS, GPS_EVENT_ROUTE_HEADER, GPS_EVENT_WAYPOINT,
	 * GPS_EVEN_ROUTE_LINK_DATA and GPS_EVENT_XFER_COMPLETE), as this
	 * might interfere with the download processing.
	 *
	 * @throws GPSException if the connection is not open
	 * @see #startDownloadRoutes()
	 */
	
	public final void downloadRoutes() throws GPSException
	{
		if( !connected ) {
			throw( new GPSConnectionException( GPSConnectionException.EXCEPTION_NOTCONNECTED ) );
		} else {
			downloadRoutesImpl();
		}
	}
	
	
	/**
	 * <p>Start Downloading Routes from the GPS unit. This is used if you want to handle
	 * your own event management rather than use the downloadRoutes() call.</p>
	 *
	 * <p>You will receive a GPS_EVENT_RECORDS event first which tells you how the total number of relevant
	 * events you will receive (GPS_EVENT_ROUTE_HEADER, GPS_EVENT_WAYPOINT and GPS_EVENT_ROUTE_LINK_DATA events).
	 * You will then receive a number GPS_EVENT_ROUTE_HEADER, GPS_EVENT_WAYPOINT and GPS_EVENT_ROUTE_LINK_DATA 
	 * events (base on what you got in the GPS_EVENT_RECORDS event), and finally a GPS_EVENT_XFER_COMPLETE 
	 * event when all routes have been sent to you by the GPS.</p>
	 *
	 *<p> NOTE:  If you had an interrupted transfer earlier, you may receive various route download events prior to
	 * getting a GPS_EVENT_RECORDS, and the number of route events received may not match the count in GPS_EVENT_RECORDS when
	 * you finally receive the GPS_EVENT_XFER_COMPLETE event.  You need to be able to handle this situation</p>
	 *
	 * <p>Basically, you need to implement a GPSListener onGPSEvent() method that listens for these events
	 * and take appropriate actions as you receive the events. </p> 
	 *
	 * <p>For an example of how this is done, you can look at the implementation of the downloadRoutes()
	 * call in the GarminGPSImpl.java source code, which manages these events under the covers.</p>
	 *
	 * @throws GPSConnectionException if the connection is not open
	 * @see #downloadRoutes()
	 */
	
	public final void startDownloadRoutes() throws GPSException
	{
		if( !connected ) {
			throw( new GPSConnectionException( GPSConnectionException.EXCEPTION_NOTCONNECTED ) );
		} else {
			startDownloadRoutesImpl();
		}
	}
	
	
	/**
	 * Upload a vector of Routes to the GPS unit.  
	 *
	 * @throws GPSConnectionException if the connection is not open
	 */
	
	public final void uploadRoutes( Vector routes ) throws GPSException
	{
		if( !connected ) {
			throw( new GPSConnectionException( GPSConnectionException.EXCEPTION_NOTCONNECTED ) );
		} else {
			uploadRoutesImpl( routes );
		}
	}
	
	
	//***************************************************************************
	//*
	//*    Track Log Class Methods
	//*
	//***************************************************************************/
	
	/**
	 * Download Track Logs from the GPS unit.  The
	 * GPS_EVENT_TRACK_TRANSFER_COMPLETE event will be generated, with a vector
	 * of tracks as the event object, when the download is done.  If an error occured
	 * the event object will be a GPSException with details of what caused the problem. You should not
	 * normally have event listeners registered for the more granular track log events if you
	 * use this method (ie. GPS_EVENT_RECORDS, GPS_EVENT_TRACK_HEADER, GPS_EVENT_TRACK_POINT,
	 * and GPS_EVENT_XFER_COMPLETE), as this might interfere with the download processing.
	 *
	 * @throws GPSException if the connection is not open
	 * @see #startDownloadTracks()
	 */
	
	public final void downloadTracks() throws GPSException
	{
		if( !connected ) {
			throw( new GPSConnectionException( GPSConnectionException.EXCEPTION_NOTCONNECTED ) );
		} else {
			downloadTracksImpl();
		}
	}
	
	
	/**
	 * <p>Start Downloading Tracks from the GPS unit. This is used if you want to handle
	 * your own event management rather than use the downloadTracks() call.</p>
	 *
	 * <p>You will receive a GPS_EVENT_RECORDS event first which tells you how the total number of relevant
	 * events you will receive (GPS_EVENT_TRACK_HEADER and GPS_EVENT_TRACK_POINT events).
	 * You will then receive a number GPS_EVENT_TRACK_HEADER and GPS_EVENT_TRACK_POINT 
	 * events (base on what you got in the GPS_EVENT_RECORDS event), and finally a GPS_EVENT_XFER_COMPLETE 
	 * event when all track logs have been sent to you by the GPS.</p>
	 *
	 *<p> NOTE:  If you had an interrupted transfer earlier, you may receive various route download events prior to
	 * getting a GPS_EVENT_RECORDS, and the number of route events received may not match the count in GPS_EVENT_RECORDS when
	 * you finally receive the GPS_EVENT_XFER_COMPLETE event.  You need to be able to handle this situation</p>
	 *
	 * <p>Basically, you need to implement a GPSListener onGPSEvent() method that listens for these events
	 * and take appropriate actions as you receive the events. </p> 
	 *
	 * <p>For an example of how this is done, you can look at the implementation of the  downloadTracks()
	 * call in the GarminGPSImpl.java source code, which manages these events under the covers.</p>
	 *
	 * @throws GPSConnectionException if the connection is not open
	 * @see #downloadTracks()
	 */
	
	public final void startDownloadTracks() throws GPSException
	{
		if( !connected ) {
			throw( new GPSConnectionException( GPSConnectionException.EXCEPTION_NOTCONNECTED ) );
		} else {
			startDownloadTracksImpl();
		}
	}
	
	
	/**
	 * Upload a vector of Tracks to the GPS unit.  
	 *
	 * @throws GPSConnectionException if the connection is not open
	 */
	
	public final void uploadTracks( Vector tracks ) throws GPSException
	{
		if( !connected ) {
			throw( new GPSConnectionException( GPSConnectionException.EXCEPTION_NOTCONNECTED ) );
		} else {
			uploadTracksImpl( tracks );
		}
	}
	
	
	//***************************************************************************
	//*
	//*    Listener Methods
	//*
	//***************************************************************************/
	
	/**
	 * Add a GPS Event listener.  If the listener already exists, then the GPSEvent types
	 * will be changed to the new value, that is, the listener will only receive one
	 * copy of each event propagated.
	 *
	 * @param		listener		the GPSListener to be added
	 * @param		types			GPSEvent types (or'ed together) listener should receive
	 */
	
	public void addGPSListener( GPSListener listener, int types ) 
	{
		int					index;
		GPSListenerEntry	entry;
		int					oldTypes;
		
		index = findListener( listener );
		
		if( index >= 0 ) {
			entry = (GPSListenerEntry)listeners.get( index );
			oldTypes = entry.getTypes();
			entry.setTypes( types );
			updateGPSListenerEvents();
			changeListenerNotify( listener, oldTypes, types );
		} else {
			listeners.add( new GPSListenerEntry( listener, types ) );
			updateGPSListenerEvents();
			addListenerNotify( listener, types );
		}
	}
	
	
	/**
	 * Remove a GPS Event listener
	 *
	 * @param		listener		the GPSListener to be removed
	 */
	
	public void removeGPSListener( GPSListener listener ) 
	{
		int					index;
		int					oldTypes;
		
		
		index = findListener( listener );
		
		if( index >= 0 ) {
			oldTypes = ((GPSListenerEntry)listeners.get( index )).getTypes();
			listeners.del( index );
			updateGPSListenerEvents();
			removeListenerNotify( listener, oldTypes );
		} 
	}
	
	
	/**
	 * Find a GPS Event listener.  The index of the listener will be returned.  
	 * -1 will be returned if the listener is not found.
	 *
	 * @param		listener		the GPSListener to be found
	 * @return						the index of the listener. -1 if not found
	 */
	
	private int findListener( GPSListener listener )
	{
		int		index		= -1;
		int		i;
		int		count;
		
		count = listeners.getCount();
		
		for( i = 0; i < count; i++ ) {
			if( ((GPSListenerEntry)listeners.get( i )).getListener() == listener ) {
				index = i;
				break;
			}
		}
		
		return( index );
	}
	
	
	/**
	 * Update which events are currently being listened to
	 *
	 */
	
	private void updateGPSListenerEvents()
	{
		int		i;
		int		count;
		
		count = listeners.getCount();
		
		listenerEvents = 0;
		
		for( i = 0; i < count; i++ ) {
			listenerEvents |= ((GPSListenerEntry)listeners.get( i )).getTypes();
		}
	}
	
	
	/**
	 * Test if event of given type is being listened to
	 * If you test for more than one type at once, then the result is whether
	 * any of the specified events is being listened for.
	 *
	 * @param		types			type of event
	 * @return						true if event is being listened for
	 */
	
	public boolean isEventListened( int type )
	{
		boolean	ret	= false;
		
		ret = (listenerEvents & type ) > 0;
		
		return( ret );
	}
	
	
	/**
	 * postGPSEvent is used by subclasses of GPS to post GPSEvents to the event queue
	 *
	 * @param		event		GPSEvent to be propagated
	 */
	
	public void postGPSEvent( GPSEvent event ) 
	{
		int					i;
		int					count;
		GPSListenerEntry	entry;
		boolean				handled 		= false;
		boolean				parsed 			= false;
		int					parseError;
		Object				eventValue;
		
		eventValue	= event.getValue();
		
		switch( event.getType() ) {
			case GPSEvent.GPS_EVENT_POSITION:
				lastPosition = (GPSPosition)eventValue;	
				break;
				
			case GPSEvent.GPS_EVENT_VELOCITY:
				lastVelocity = (GPSVelocity)eventValue;	
				break;
				
			case GPSEvent.GPS_EVENT_TIME:
				lastTime = (GPSTime)eventValue;	
				break;
				
			default:
				break;
		}
		
		if( Compile.EVENT_QUEUE_SIZE > 0 && eventQueue.getCount() >= Compile.PACKET_QUEUE_SIZE ) {
			eventQueue.del( 0 );
		} 

		eventQueue.add( event );
	}
	
	
	/**
	 * propagateGPSEvents is used by propagate queued GPSEvents to listeners
	 * interested in that specific type of event.  If the max number of events is <= 0
	 * then all currently queued events are propagated (ie. the queue is emptied)
	 *
	 * @param	num		max number of events to propagate at one time
	 */
	
	private void propagateGPSEvents( int num ) 
	{
		int					i;
		int					eventsPropagated 	= 0;
		int					count;
		GPSEvent			event;
		GPSListenerEntry	entry;
		boolean				handled 			= false;
		Object				eventValue;
		boolean				propagate;
		int					type;
		
		while( ( num <= 0 || eventsPropagated < num ) && eventQueue.getCount() > 0 ) {
			event = (GPSEvent)eventQueue.get( 0 );
			eventQueue.del( 0 );
			eventsPropagated++;
			
			count 		= listeners.getCount();
			propagate 	= true;
			
			for( i = 0; i < count; i++ ) {
				entry = (GPSListenerEntry)listeners.get( i );
				type = event.getType();
				if( ( type & entry.getTypes() ) != 0 ) {
					eventValue = event.getValue();
					if( eventValue instanceof Parseable && !((Parseable)eventValue).isParsed() ) {
						try {
							((Parseable)eventValue).parse();
							propagate = !blockUnfilledPackets() || ((Parseable)eventValue).isDataValid();
						}
						catch( GPSException exception ) {
							propagate = true;
							event = new GPSEvent( GPSEvent.GPS_EVENT_ERROR, ((Parseable)eventValue).toString(), exception );
						} 
					}
					if( propagate ) {
						handled = entry.getListener().onGPSEvent( event );
						if( handled ) {
							break;
						}
					}
				}
				
				if( serialPort != null ) {
					serialPort.spinEventLoop();				// Spin the VM's event loop after each event
				}
			}
		}
	}
	
	
	/**
	 * Block unfilled packet propagations.  That is, do not propagate packets
	 * that contain only zero values. Default is to block such packets (true).
	 *
	 * @return					Block unfilled packet propagations - true/false
	 */
	
	public boolean blockUnfilledPackets() 
	{
		return( blockUnfilledPackets );
	}
	
	
	/**
	 * Set Block unfilled packet propagations
	 *
	 * @param		block		Block unfilled packet propagations?
	 */
	
	public void setBlockUnfilledPackets( boolean block ) 
	{
		blockUnfilledPackets = block;
	}
	
	
	//***************************************************************************
	//*
	//* 	Comm Abstract Methods (implemented/overriden by protocol-specific subclasses)
	//*
	//***************************************************************************/

	/**
	 * Connecting the GPS.  This method should be implemented by protocol-specific
	 * subclasses to perform any unique connection processing they may require.
	 * This method will be called BEFORE the serial port is connected.
	 */
	
	protected abstract void connectingNotify() throws GPSSerialPortException, GPSOperationUnavailableException;
	
	
	/**
	 * GPS Connected.  This method should be implemented by protocol-specific
	 * subclasses to perform any unique connection processing they may require.
	 * This method will be called AFTER the serial port is connected.
	 */
	
	protected abstract void connectedNotify();
	
	
	/**
	 * Disconnecting the GPS.  This method should be implemented by protocol-specific
	 * subclasses to perform any unique disconnection processing they may require.
	 * This method will be called BEFORE the serial port is disconnected.
	 */
	
	protected abstract void disconnectingNotify();
	
	
	/**
	 * GPS  Disconnected.  This method should be implemented by protocol-specific
	 * subclasses to perform any unique disconnection processing they may require.
	 * This method will be called AFTER the serial port is disconnected.
	 */
	
	protected abstract void disconnectedNotify();
	
	
	/**
	 * Check if a byte is a Begin Packet.  This method should be implemented by protocol-specific
	 * subclasses to return true/false.
	 *
	 * @param	chr		byte to be checked
	 * @return			is byte begin packet?
	 */
	
	protected abstract boolean isBeginPacketByte( byte chr );
	
	
	/**
	 * Check if a byte is a End Packet.  This method should be implemented by protocol-specific
	 * subclasses to return true/false.
	 *
	 * @param	chr		byte to be checked
	 * @return			is byte end packet?
	 */
	
	protected abstract boolean isEndPacketByte( byte chr );
	
	
	/**
	 * Check if a duplicate End Packet byte should be ignored, that is, treated as an "escape"
	 * sequence to allow embedding of and End Packet byte in the data catalog.  
	 * This method should be implemented by protocol-specific
	 * subclasses to return true/false.
	 *
	 * @return			ignore duplicate end byte?
	 */
	
	protected abstract boolean ignoreDuplicateEndByte();
	
	
	/**
	 * Inflate (duplicate) End bytes in buffer 
	 * This method should be implemented by protocol-specific
	 * subclasses to return true/false.
	 *
	 * @param	writeBuffer		buffer to put the inflated data into
	 * @param	buffer			original data
	 * @param	offset			starting point in original data buffer
	 * @param	length			length of original data buffer
	 * @param	maxLen			max length of write buffer
	 * @return					# bytes copied to writeBuffer, -1 if error
	 */
	
	protected abstract int inflateDuplicateEndBytes( byte[] writeBuffer, byte[] buffer, int offset, int length, int maxLen ) throws GPSSerialPortException;
	
	
	/**
	 * Append a trailing byte after the end byte?  
	 * This method should be implemented by protocol-specific
	 * subclasses to return true/false.
	 *
	 * @return			append a trailing end byte?
	 */
	
	protected abstract boolean appendTrailingEndByte();
	
	
	/**
	 * Get the trailing byte after the end byte?  
	 * This method should be implemented by protocol-specific
	 * subclasses to return true/false.
	 *
	 * @return			the a trailing end byte
	 */
	
	protected abstract byte getTrailingEndByte();
	
	
	/**
	 * onPacketNotify will be called when a complete packet has been read from the serial port
	 * This method should be implemented by protocol-specific
	 * subclasses to perform any unique packet processing they may require.  The packet data is
	 * only guaranteed to be valid for the duration of the call...so if it needs to be persistent,
	 * in the packet, the method should copy the data to another storage area.
	 *
	 * @param		packet		byte array for packet
	 * @param		len			number of bytes in packet
	 */
	
	protected abstract void onPacketNotify( byte[] packet, int len );
	
	
	/**
	 * process a GPS Init Packet.
	 * This method should be overridden by protocol-specific
	 * subclasses to perform any unique packet processing they may require.
	 *
	 * @param		packet		packet object
	 */
	
	public void processInitPacket( Object packet ) 
	{
		// Do nothing by default
	}
	
	
	/**
	 * process a GPS Capabilities Packet
	 * This method should be overridden by protocol-specific
	 * subclasses to perform any unique packet processing they may require.
	 *
	 * @param		packet		packet object
	 */
	
	public void processCapabilitiesPacket( Object packet ) 
	{
		// Do nothing by default
	}
	
	
	//***************************************************************************
	//*
	//* 	Listener Abstract Methods (implemented/overriden by protocol-specific subclasses)
	//*
	//***************************************************************************/
	
	/**
	 * Add Listener.  This method can be overriden by protocol-specific
	 * subclasses to perform any unique add listener processing they may require.
	 * This method will beinvoked AFTER the listener has been added. Default is to
	 * do nothing
	 *
	 * @param		listener		the GPSListener to be added
	 * @param		types			GPSEvent types (or'ed together) listener will receive
	 */
	
	protected void addListenerNotify( GPSListener listener, int types ) {
		
	}
	
	
	/**
	 * Remove Listener.  This method can be overriden by protocol-specific
	 * subclasses to perform any unique remove listener processing they may require.
	 * This method will beinvoked AFTER the listener has been removed. Default is to
	 * do nothing
	 *
	 * @param		listener		the GPSListener to be removed
	 * @param		types			GPSEvent types (or'ed together) listener received
	 */
	
	protected void removeListenerNotify( GPSListener listener, int types ) {
		
	}
	
	
	/**
	 * Change Listener.  This method can be overriden by protocol-specific
	 * subclasses to perform any unique change listener processing they may require.
	 * This method will beinvoked AFTER the listener's types has been changed. Default is to
	 * do nothing
	 *
	 * @param		listener		the GPSListener to be changed
	 * @param		oldTypes		GPSEvent types (or'ed together) listener used to receive
	 * @param		newTypes		new GPSEvent types (or'ed together) listener will receive
	 */
	
	protected void changeListenerNotify( GPSListener listener, int oldTypes, int newTypes ) {
		
	}
	
	
	//***************************************************************************
	//*
	//* 	Accessor Abstract Methods (implemented/overriden by protocol-specific subclasses)
	//*
	//***************************************************************************/
	
	/**
	 * Get Manufacturer ID
	 *
	 * @return   Manufacturer Id
	 * @throws GPSException
	 */
	
	public String getManufacturerID() throws GPSException
	{
		throw( new GPSFunctionUnavailableException() );
	}
	
	
	/**
	 * Get Product ID
	 *
	 * @return   Product Id
	 * @throws GPSException
	 */
	
	public int getProductID() throws GPSException
	{
		throw( new GPSFunctionUnavailableException() );
	}
	
	
	/**
	 * Get Description
	 *
	 * @return   Description
	 * @throws GPSException
	 */
	
	public String getDescription() throws GPSException
	{
		throw( new GPSFunctionUnavailableException() );
	}
	
	
	/**
	 * Get Software Version
	 *
	 * @return   Software Version
	 * @throws GPSException
	 */
	
	public int getSoftwareVersion() throws GPSException
	{
		throw( new GPSFunctionUnavailableException() );
	}
	
	
	
	//***************************************************************************
	//*
	//* 	Waypoint Abstract Methods (implemented/overriden by protocol-specific subclasses)
	//*
	//***************************************************************************/
	
	/**
	 * Download Waypoints from the GPS unit into an array.  This method should be overriden
	 * by protocol-specific subclasses. The default behaviour posts a Function Unavailable error.
	 *
	 * @throws GPSException
	 */
	
	protected void downloadWaypointsImpl() throws GPSException
	{
		throw( new GPSFunctionUnavailableException() );
	}
	
	
	/**
	 * Start Downloading Waypoints from the GPS unit.  This method should be overriden
	 * by protocol-specific subclasses. The default behaviour posts a Function Unavailable error.
	 *
	 * @return		false if the request failed.
	 * @throws GPSException
	 */
	
	protected boolean startDownloadWaypointsImpl() throws GPSException
	{
		throw( new GPSFunctionUnavailableException() );
	}
	
	
	/**
	 * Upload an array of Waypoints to the GPS unit.  This method should be overriden
	 * by protocol-specific subclasses. The default behaviour posts a Function Unavailable error.
	 *
	 * @param	waypoints   	An array of waypoints
	 * @throws GPSException
	 */
	
	protected void uploadWaypointsImpl( GPSWaypoint[] waypoints )  throws GPSException
	{
		throw( new GPSFunctionUnavailableException() );	
	}
	
	
	//***************************************************************************
	//*
	//* 	Route Abstract Methods (implemented/overriden by protocol-specific subclasses)
	//*
	//***************************************************************************/
	
	/**
	 * Download Routes from the GPS unit.  This method should be overriden 
	 * by protocol-specific subclasses. The default behaviour posts a Function Unavailable error.
	 *
	 * @throws GPSException
	 */
	
	protected void downloadRoutesImpl()  throws GPSException
	{
		throw( new GPSFunctionUnavailableException() );	
	}
	
	
	/**
	 * Start downloading Routes from the GPS unit.  This method should be overriden 
	 * by protocol-specific subclasses. The default behaviour posts a Function Unavailable error.
	 *
	 * @return		false if the request failed.
	 * @throws GPSException
	 */
	
	protected boolean startDownloadRoutesImpl()  throws GPSException
	{
		throw( new GPSFunctionUnavailableException() );	
	}
	
	
	/**
	 * Upload a vector of Routes to the GPS unit.  This method should be overriden 
	 * by protocol-specific subclasses. The default behaviour posts a Function Unavailable error.
	 *
	 * @throws GPSException
	 */
	
	protected void uploadRoutesImpl( Vector routes )  throws GPSException
	{
		throw( new GPSFunctionUnavailableException() );		
	}
	
	
	//***************************************************************************
	//*
	//* 	Track Log Abstract Methods (implemented/overriden by protocol-specific subclasses)
	//*
	//***************************************************************************/
	
	/**
	 * Download Track Logs from the GPS unit.  This method should be overriden 
	 * by protocol-specific subclasses. The default behaviour posts a Function Unavailable error.
	 *
	 * @throws GPSException
	 */
	
	protected void downloadTracksImpl()  throws GPSException
	{
		throw( new GPSFunctionUnavailableException() );	
	}
	
	
	/**
	 * Start downloading Tracks from the GPS unit.  This method should be overriden 
	 * by protocol-specific subclasses. The default behaviour posts a Function Unavailable error.
	 *
	 * @return		false if the request failed.
	 * @throws GPSException
	 */
	
	protected boolean startDownloadTracksImpl()  throws GPSException
	{
		throw( new GPSFunctionUnavailableException() );	
	}
	
	
	/**
	 * Upload a vector of Tracks to the GPS unit.  This method should be overriden 
	 * by protocol-specific subclasses. The default behaviour posts a Function Unavailable error.
	 *
	 * @throws GPSException
	 */
	
	protected void uploadTracksImpl( Vector tracks )  throws GPSException
	{
		throw( new GPSFunctionUnavailableException() );		
	}
	
	
	//***************************************************************************
	//*
	//* 	Serial Port High Level Communications Methods
	//*
	//***************************************************************************/
	
	/**
	 * Set the port for serial communications.  The default port is set to
	 * 0.
	 *
	 * @param		port	   serial port number
	 * @throws GPSOperationUnavailableException
	 */
	
	public void setSerialPort( int port ) throws GPSOperationUnavailableException
	{
		if( connected || port < 0 ) {
			throw( new GPSOperationUnavailableException() );
		} else {
			if( port > 0 ) {
				if( port <= 8 ) {
					serialPortNumber = port - 1;
				} else {
					serialPortNumber = port;
				}
			} else {
				serialPortNumber = 0;
			}
		} 
	}
	
	
	/**
	 * Set the port for serial communications to a byte array.  
	 * This will set the serial port a special value internally and read data from
	 * the byte array directly.  NOTE:  The data in the byte array must be
	 * formatted exactly as it would come in off the serial port for the 
	 * protocol that you are using (eg. NMEA, Garmin, etc.)
	 *
	 * @param		data	   byte array of GPS data
	 * @param		size	   size of the data in the byte array
	 * @param		offset	   offest of data in the byte array (start point)
	 * @throws GPSOperationUnavailableException
	 */
	
	public void setSerialPort( byte[] data, int size, int offset ) throws GPSOperationUnavailableException
	{
		if( connected || offset < 0 || size < 0 || data.length < offset + size ) {
			throw( new GPSOperationUnavailableException() );
		} else {
			serialByteArray = new byte[ size ]; 
			
			for( int i = offset; i < size + offset; i++ ){ 
				serialByteArray[ i ] = data[ offset + i ]; 
			} 
			
			serialPortNumber = SERIAL_PORT_ALTERNATE_INPUT;
			serialByteArraySize = size;
			serialByteArrayOffset = 0 ;
		} 
	}
	
	
	/**
	 * Set the port for serial communications to a byte array.  
	 * This will set the serial port a special value internally and read data from
	 * the byte array directly.  NOTE:  The data in the byte array must be
	 * formatted exactly as it would come in off the serial port for the 
	 * protocol that you are using (eg. NMEA, Garmin, etc.)
	 *
	 * @param		data	   byte array of GPS data
	 * @param		size	   size of data to use in the byte array
	 * @throws GPSOperationUnavailableException
	 */
	
	public void setSerialPort( byte[] data, int size ) throws GPSOperationUnavailableException
	{
		setSerialPort( data, size, 0 );
	}
	
	
	/**
	 * Set the port for serial communications to a byte array.  
	 * This will set the serial port a special value internally and read data from
	 * the byte array directly.  NOTE:  The data in the byte array must be
	 * formatted exactly as it would come in off the serial port for the 
	 * protocol that you are using (eg. NMEA, Garmin, etc.)
	 *
	 * @param		data	   byte array of GPS data
	 * @param		size	   size of data to use in the byte array
	 * @param		offset	   offest of data in the byte array (start point)
	 * @param		maxRead	   max number of bytes to read at a time from the array
	 * @throws GPSOperationUnavailableException
	 */
	
	public void setSerialPort( byte[] data, int size, int offset, int maxRead ) throws GPSOperationUnavailableException
	{
		if( maxRead <= 0 ) {
			throw( new GPSOperationUnavailableException() );
		} else {
			setSerialPort( data, size, offset );
			serialAlternateMaxReadLen = maxRead;
		}
	}
	
	
	/**
	 * Set the port for serial communications to a Catalog.  
	 * This will set the serial port a special value internally and read data from
	 * the Catalog file.  NOTE:  The data in the catalog must be
	 * formatted exactly as it would come in off the serial port for the 
	 * protocol that you are using (eg. NMEA, Garmin, etc.)
	 *
	 * @param		catalog	   Catalog to be read from.
	 * @throws GPSOperationUnavailableException
	 */
	
	public void setSerialPort( Catalog catalog ) throws GPSOperationUnavailableException
	{
		if( connected || catalog == null ) {
			throw( new GPSOperationUnavailableException() );
		} else {
			serialCatalog = catalog; 
			
			serialPortNumber 					= SERIAL_PORT_ALTERNATE_INPUT;
			serialCatalogRecord					= 0;
			serialCatalogEOF					= false;
			
			serialCatalog.setRecordPos( serialCatalogRecord );
			
			serialCatalogRecordBytesLeft		= serialCatalog.getRecordSize();		
		} 
	}
	
	/**
	 * Get the baud rate for serial communications.  
	 *
	 * @return		baud rate
	 */
	
	public int getSerialBaudRate()
	{
		return( serialBaudRate );
	}
	
	
	/**
	 * Set the baud rate for serial communications.  The default rate is set to
	 * NMEA standards, 4800 baud.
	 *
	 * @param		rate		baud rate
	 * @throws GPSOperationUnavailableException
	 */
	
	public void setSerialBaudRate( int rate ) throws GPSOperationUnavailableException
	{
		if( connected ) {
			throw( new GPSOperationUnavailableException() );
		} else {
			serialBaudRate = rate;
		} 
	}
	
	
	/**
	 * Set the read timeout for serial communications.  The default timeout is set to
	 * 100 milliseconds.
	 *
	 * @param		timeout 	   read timeout
	 * @throws GPSOperationUnavailableException
	 */
	
	public void setSerialReadTimeout( int timeout ) throws GPSOperationUnavailableException
	{
		if( connected ) {
			throw( new GPSOperationUnavailableException() );
		} else {
			serialReadTimeout = timeout;
		}   
	}
	
	
	/**
	 * Set whether to use CheckRead on the serial port.  The default timeout is to
	 * check for queued bytes on the port.
	 *
	 * @param		checkread 	   Whether to do Serial CheckRead or not
	 */
	
	public void setSerialCheckRead( boolean checkread )
	{
		serialCheckRead = checkread;
	}
	
	
	/**
	 * Set the serial buffer size
	 *
	 * @param		size		buffer size
	 * @throws GPSOperationUnavailableException
	 */
	
	public void setSerialReadBufferSize( int size ) throws GPSOperationUnavailableException
	{
		if( connected ) {
			throw( new GPSOperationUnavailableException() );
		} else {
			serialReadBufferSize = size;
		}  
	}
	
	
	/**
	 * Set the packet buffer size
	 *
	 * @param		size		buffer size
	 * @throws GPSOperationUnavailableException
	 */
	
	public void setSerialPacketSize( int size ) throws GPSOperationUnavailableException
	{
		if( connected ) {
			throw( new GPSOperationUnavailableException() );
		} else {
			packetBufferSize = size;
		} 
	}
	
	
	/**
	 * Open the serial port
	 *
	 * @throws GPSSerialPortException
	 */
	
	private void openSerial() throws GPSSerialPortException
	{
		if( serialPortNumber != SERIAL_PORT_ALTERNATE_INPUT ) {
			serialPort = new SerialPort( serialPortNumber, serialBaudRate );
		} else {
			serialAlternateOpen = true;
		}
		if( !serialPortIsOpen() ) {
			throw( new GPSSerialPortException( GPSSerialPortException.EXCEPTION_OPEN ) );
		} else {
			serialPortSetReadTimeout( serialReadTimeout );
			serialPortSetFlowControl( false );			// GPS units do not use handshaking!!
			startPolling();
		}
	}
	
	/**
	 * Close the serial port
	 *
	 * @throws GPSSerialPortException
	 */
	
	private void closeSerial() throws GPSSerialPortException
	{
		if( serialPortIsOpen() ) {
			stopPolling();
			serialPortClose();
			if( serialPortIsOpen() ) {				
				throw( new GPSSerialPortException( GPSSerialPortException.EXCEPTION_CLOSE ) );
			} 
		} 
	}
	
	
	/**
	 * Start polling the GPS unit
	 */
	
	private void startPolling()
	{
		serialReadBuffer 	= new byte[ serialReadBufferSize ];
		packetBuffer 		= new byte[ packetBufferSize ];
		packetBufferPos 	= 0;
		
		serialState = SERIAL_STATE_PACKET_BEGIN;
	}
	
	
	/**
	 * Stop polling the GPS Unit
	 */
	
	private void stopPolling()
	{
		serialState = SERIAL_STATE_STOPPED;
		
		serialReadBuffer 	= null;
		packetBuffer 		= null;
		packetBufferPos 	= 0;
	}
	
	
	/**
	 * Poll the GPS unit and process all queued packets/events in one fell swoop (not thread safe!)
	 *
	 * @throws GPSSerialPortException
	 */
	
	public void poll() throws GPSSerialPortException
	{
		poll( true );
	}
	
	
	/**
	 * Poll the GPS unit 
	 *
	 * @param	processEvents 	process all queued packets/events after polling GPS unit?
	 *
	 * @throws GPSSerialPortException
	 */
	
	public synchronized void poll( boolean processEvents ) throws GPSSerialPortException
	{
		int			available;
		int			toRead;
		int			numRead;
		char		chr;
		int			bufferSpace;
		
		bufferSpace = serialReadBufferSize - serialReadBufferStart;
		
		if( serialState != SERIAL_STATE_STOPPED ) {
			if( serialCheckRead ) {
				toRead 		= 0;
				available 	= serialPortReadCheck();
				if( available > 0 ) {
					if( available < bufferSpace ) {
						toRead = available;
					} else {
						toRead = bufferSpace;
					}
				} 
			} else {
				toRead = bufferSpace;
			}
			
			if( toRead > 0 ) {
				numRead = serialPortReadBytes( serialReadBuffer, serialReadBufferStart, toRead );
				if( numRead < 0 ) {
					numRead = 0;
				}
				numRead += serialReadBufferStart;
				serialReadBufferStart = 0;
				if( numRead > 0 ) {
					if( Compile.DEBUG_DUMPSERIALBUFFER ) {
						if( display != null ) {
							nr = numRead;
							debugDisplay( "Serial Buffer - Bytes Read: " + numRead );
							dumpSerialBuffer();
						}
					}
					processBytes( numRead );
				} 
			}		
		}
		
		if( processEvents ) {
			processEvents();
		}
		
		if( Compile.DEBUG ) {
			if( display != null && eventQueue.getCount() > 0 ) {
				debugDisplay( "Event Queue size: " + eventQueue.getCount() );
			}
		}
	}
	
	
	/**
	 * Process all queued packets/events in one fell swoop
	 */
	
	public void processEvents()
	{
		byte[]		packet						= null;
		boolean		done						= false;
		int			packetsProcessed			= 0;
		
		// Process queued GPS Packets
		
		if( Compile.PACKET_QUEUE_SIZE != 0 ) {
			while( ( Compile.MAX_PACKETS_PROCESSED_PER_POLL <= 0 || packetsProcessed < Compile.MAX_PACKETS_PROCESSED_PER_POLL ) && !done ) {
				synchronized( packetQueue ) {
					if( packetQueue.getCount() > 0 ) {
						packet = (byte[])packetQueue.get( 0 );
						packetQueue.del( 0 );
						packetsProcessed++;
					} else {
						done = true;
					}
				}
				
				if( !done ) {
					onPacketNotify( packet, packet.length );
					
					if( serialPort != null ) {
						serialPort.spinEventLoop();				// Spin the VM's event loop after each packet
					}
				}
			}
		}

		// Propagate queued GPS Events
		
		propagateGPSEvents( Compile.MAX_EVENTS_PROPAGATED_PER_POLL );
	}
	

	
	static int	nr	= 0;
	
	public void dumpSerialBuffer()
	{
		StringBuffer displ = new StringBuffer();
		StringBuffer str = new StringBuffer();
		
		if( display != null ) {
			
			debugDisplay( "# Last Read: " + nr );	
			for( int i = 0; i < nr; i++ ) {
				str.append( (char)serialReadBuffer[ i ] );
				if( ( i % 15 == 0 && i != 0 ) || i == nr - 1 ) {
					debugDisplay( "     - " + str.toString() );
					str.setLength( 0 );
				} 
			}
			for( int i = 0; i < nr; i++ ) {
				displ.append( " " + charToHex( (char)serialReadBuffer[ i ] ) );
				if( ( i % 10 == 0 && i != 0 ) || i == nr - 1 ) {
					debugDisplay( "     x" + displ.toString() );
					displ.setLength( 0 );	
				} 
			}
		}
	}
	
	
	protected String charToHex( char chr )
	{
		char[] hex = new char[ 2 ];
		
		hex[ 0 ] = HEX_CHARS[ ( ( chr + 256 ) & 0xf0 ) >> 4 ];
		hex[ 1 ] = HEX_CHARS[ ( chr + 256 ) & 0x0f ];
		
		return( new String( hex ) );
	}
	
	
	public void dumpPacketBuffer()
	{
		StringBuffer str = new StringBuffer();
		
		if( display != null ) {
			
			debugDisplay( "Packet Buffer: " );		
			for( int i = 0; i < packetBufferPos; i++ ) {
				if( ( i % 15 == 0 && i != 0 ) || i == packetBufferPos - 1 ) {
					str.append( (char)packetBuffer[ i ] );
					debugDisplay( "     - " + str.toString() );
					str.setLength( 0 );
				} else {
					str.append( (char)packetBuffer[ i ] );
				}
			}
		}
		
	}
	
	
	/**
	 * Process bytes read from serial port
	 *
	 * @param	numRead		number of bytes read into buffer
	 * @throws GPSPacketException
	 */
	
	private void processBytes( int numRead )  throws GPSSerialPortException
	{
		try {
			switch( serialState ) {
				case SERIAL_STATE_PACKET_BEGIN:
					constructPacket( findPacketBegin( 0, numRead ), numRead );
					break;
					
				case SERIAL_STATE_PACKET_DOUBLE:
				case SERIAL_STATE_PACKET_END:
					constructPacket( 0, numRead );	
					break;
					
				default:
					throw( new GPSSerialPortException( GPSSerialPortException.EXCEPTION_STATE ) );	
			}
		}
		catch( GPSSerialPortException exception ) {
			serialState = SERIAL_STATE_PACKET_BEGIN;
			packetBufferPos = 0;
			throw( exception );
		}
		
	}
	
	
	/**
	 * Find Packet Begin in byte catalog
	 *
	 * @param	start	starting position in buffer for check
	 * @param	len		length of byte string to check
	 */
	
	private int findPacketBegin( int start, int len )
	{
		int		i;
		int		pos = -1;
		byte	chr;
		
		for( i = start; i < len; i++ ) {
			chr = serialReadBuffer[ i ];
			if( isBeginPacketByte( chr ) ) {
				if( isEndPacketByte( chr ) ) {
					if( i < len - 1 ) {
						chr = serialReadBuffer[ i + 1 ];
						if( !isEndPacketByte( chr ) && appendTrailingEndByte() && chr != getTrailingEndByte() ) {
							pos = i;
							break;
						}
					} else {
						serialReadBuffer[ 0 ] = chr;
						serialReadBufferStart = 1;
						break;
					}
				} else {
					pos = i;
					break;
				}
			}
		}
		
		return( pos );
	}
	
	/**
	 * Construct packet from bytes
	 *
	 * @param	start 	starting index in serial buffer.  -1 = ignore
	 * @param	len		length of byte string to check
	 * @throws GPSSerialPortException
	 */
	
	private void constructPacket( int start, int len ) throws GPSSerialPortException
	{
		int		i;
		byte	chr;
		
		if( start >= 0 ) {
			for( i = start; i < len; i++ ) {
				if( packetBufferPos < packetBufferSize ) {
					chr	=  serialReadBuffer[ i ];
					if( serialState == SERIAL_STATE_PACKET_DOUBLE ) {
						serialState = SERIAL_STATE_PACKET_END;
						if( chr != packetBuffer[ packetBufferPos - 1 ] ) {
							finishPacket( i + 1, len );
							break;
						}
					} else {
						packetBuffer[ packetBufferPos ] = chr;
						packetBufferPos++;
						if( serialState == SERIAL_STATE_PACKET_END && isEndPacketByte( chr ) ) {
							if( ignoreDuplicateEndByte() ) {
								if( i < len - 1 ) {
									if( serialReadBuffer[ i + 1 ] == chr ) {
										i++;
									} else {
										finishPacket( i + 1, len );
										break;
									}
								} else {
									serialState = SERIAL_STATE_PACKET_DOUBLE;
								}
							} else {
								if( packetBufferPos <= 2 ) {  // Null packet...ignore it!
									packetBufferPos = 0;
									serialState 	= SERIAL_STATE_PACKET_BEGIN;
									constructPacket( findPacketBegin( i + 1, len ), len );
									break;
								} else {
									finishPacket( i + 1, len );
									break;
								}
							}
						} else {
							serialState = SERIAL_STATE_PACKET_END;
						}
					}
				} else {
					throw( new GPSSerialPortException( GPSSerialPortException.EXCEPTION_BUFFER_OVERRUN ) );	
				}
			}
		}
	}
	
	
	/**
	 * Finish off a packet
	 *
	 * @param	start 	starting index in serial buffer.  
	 * @param	len		length of byte string to check
	 * @throws GPSSerialPortException
	 */
	
	private void finishPacket( int start, int len ) throws GPSSerialPortException
	{
		int			restart = start;
		
		if( appendTrailingEndByte() ) {
			if( packetBufferPos < packetBufferSize ) {
				packetBuffer[ packetBufferPos ] = getTrailingEndByte();
				packetBufferPos++;
				restart++;
			} else {
				throw( new GPSSerialPortException( GPSSerialPortException.EXCEPTION_BUFFER_OVERRUN ) );	
			}
		}
		
		serialState 	= SERIAL_STATE_PACKET_BEGIN;
		
		onPacket( packetBuffer, packetBufferPos );
		
		packetBufferPos = 0;
		
		constructPacket( findPacketBegin( restart, len ), len );
	}	
	
	
	/**
	 * Write bytes to serial port
	 *
	 * @param	buffer		data buffer to be written
	 * @param	offset		offset of byte to start writing from in buffer
	 * @param	length		number of bytes to be written
	 * @throws GPSConnectionException
	 * @throws GPSSerialPortException
	 */
	
	protected void writeSerial( byte[] buffer, int offset, int length ) throws GPSSerialPortException, GPSConnectionException
	{
		byte[]		writeBuffer;
		int			writeLen;
		int			writeOffset;
		int			written;
		
		writeBuffer	= buffer;
		writeLen	= length;
		writeOffset	= offset;
		
		if( !connected ) {
			throw( new GPSConnectionException( GPSConnectionException.EXCEPTION_NOTCONNECTED ) );
		} else {
			if( ignoreDuplicateEndByte() ) {
				writeBuffer = serialWriteBuffer;
				
				writeLen 	= inflateDuplicateEndBytes( writeBuffer, buffer, offset, length, serialWriteBufferSize );
				writeOffset = 0;
			}
			if( writeLen > 0 ) {
				written = serialPortWriteBytes( writeBuffer, writeOffset, writeLen );
				if( written != writeLen ) {						
					throw( new GPSSerialPortException( GPSSerialPortException.EXCEPTION_WRITE ) );	
				}
			}
		}
	}
	
	//***************************************************************************
	//*
	//* 	Serial Port Low Level Communications Methods - Used to redirect to a byte array
	//*
	//***************************************************************************/
	
	/**
	 * Return the serial port object
	 *
	 * @return		serial port object
	 */
	
	public SerialPort getSerialPort()
	{
		return( serialPort );
	}
	
	
	/**
	 * Check if the serial port is open
	 *
	 * @return		true if the port is open
	 */
	
	private boolean serialPortIsOpen()
	{
		boolean		open = serialAlternateOpen;
		
		if( serialPortNumber != SERIAL_PORT_ALTERNATE_INPUT ) {
			open = serialPort.isOpen();
		}
		
		return( open );
	}
	
	
	/**
	 * Set the serial port read timeout
	 *
	 * @param	timeout		the serial port read timeout
	 * @throws GPSSerialPortException
	 */
	
	private void serialPortSetReadTimeout( int timeout ) throws GPSSerialPortException
	{
		if( serialPortNumber != SERIAL_PORT_ALTERNATE_INPUT ) {
			if( !serialPort.setReadTimeout( timeout ) ) {
				throw( new GPSSerialPortException( GPSSerialPortException.EXCEPTION_SET_READ_TIMEOUT ) );	
			}
		}
	}
	
	
	/**
	 * Set the serial port flow control
	 *
	 * @param	flow		the serial port flow control
	 * @throws GPSSerialPortException
	 */
	
	private void serialPortSetFlowControl( boolean flow ) throws GPSSerialPortException
	{
		if( serialPortNumber != SERIAL_PORT_ALTERNATE_INPUT ) {
			if( !serialPort.setFlowControl( flow ) ) {
				throw( new GPSSerialPortException( GPSSerialPortException.EXCEPTION_SET_FLOW_CONTROL ) );	
			}
		}
		
	}
	
	
	/**
	 * Close the serial port
	 */
	
	private void serialPortClose()
	{
		if( serialPortNumber != SERIAL_PORT_ALTERNATE_INPUT ) {
			serialPort.close();
		} else if( serialCatalog != null ) {
			serialCatalog.close();
		}
		
		serialAlternateOpen = false;
	}
	
	
	/**
	 * Check number of bytes available to read on serial port
	 *
	 * @return	number of bytes available to read
	 * @throws	GPSSerialPortException
	 */
	
	private int serialPortReadCheck() throws GPSSerialPortException
	{
		int avail = 0;
		
		if( serialPortNumber != SERIAL_PORT_ALTERNATE_INPUT ) {
			avail = serialPort.readCheck();
			if( avail < 0 ) {
				throw( new GPSSerialPortException( GPSSerialPortException.EXCEPTION_READ_CHECK, avail, serialPort.getLastLineError() ) );
			}
		} else if( serialByteArray != null ) {
			avail = serialByteArraySize - serialByteArrayOffset;
		} else {
			avail = serialAlternateMaxReadLen;
		}
		
		return( avail );
	}
	
	
	/**
	 * Read bytes from the serial port (or byte array if we are using one)
	 *
	 * @param		buffer	   buffer to read into
	 * @param		offset	   offset to read to
	 * @param		len	   	   length of buffer
	 *
	 * @return		number of bytes read
	 * @throws		GPSSerialPortException
	 */
	
	private int serialPortReadBytes( byte[] buffer, int offset, int len ) throws GPSSerialPortException
	{
		int		read = 0;
		int		avail;
		
		if( serialPortNumber != SERIAL_PORT_ALTERNATE_INPUT ) {
			read = serialPort.readBytes( buffer, offset, len );
			if( read < 0 ) {
				throw( new GPSSerialPortException( GPSSerialPortException.EXCEPTION_READ, read, serialPort.getLastLineError() ) );
			}
		} else if( serialCatalog != null && !serialCatalogEOF ) {
			read = serialCatalog.readBytes( buffer, offset, len );
			if( read >= serialCatalogRecordBytesLeft ) {
				if( serialCatalogRecord < serialCatalog.getRecordCount() ) {
					if( serialCatalog.setRecordPos( ++serialCatalogRecord ) ) {
						serialCatalogRecordBytesLeft = serialCatalog.getRecordSize();
					} else {
						serialCatalogRecordBytesLeft = 0;
						serialCatalogEOF = true;
					}
				} else {
					serialCatalogEOF = true;
				}
			} else {
				serialCatalogRecordBytesLeft -= read;
			}

		} else if( serialByteArray != null ) {
			read = serialAlternateMaxReadLen;
			
			if( len < read ) {
				read = len;
			}
			
			avail = serialByteArraySize - serialByteArrayOffset;
			
			if( avail < read ) {
				read = avail;
			}
			
			for( int i = 0; i < read; i++ ) {
				buffer[ i + offset ] = serialByteArray[ i + serialByteArrayOffset];
			}
			
			serialByteArrayOffset += read;
		}
		
		if( Compile.DEBUG_SERIALPORT ) {
			if( read > 0 ) {
				debugDisplay( "Serial Read - bytes: " + read );
			} 
		}
		
		return( read );
	}
	
	
	/**
	 * Write bytes to the serial port.  If reading from a byte array, this is a no-op, and
	 * just returns the length passed in.
	 *
	 * @param		buffer	   buffer to write
	 * @param		offset	   offset to write from
	 * @param		len	   	   length to write
	 *
	 * @return		number of bytes written
	 * @throws 		GPSSerialPortException
	 */
	
	private int serialPortWriteBytes( byte[] buffer, int offset, int len ) throws GPSSerialPortException
	{
		int		written = len;
		
		if( serialPortNumber != SERIAL_PORT_ALTERNATE_INPUT ) {
			written = serialPort.writeBytes( buffer, offset, len );
			if( written < 0 ) {
				throw( new GPSSerialPortException( GPSSerialPortException.EXCEPTION_WRITE, written, serialPort.getLastLineError() ) );
			}
		}
		
		if( Compile.DEBUG_SERIALPORT ) {
			if( written > 0 ) {
				debugDisplay( "Serial Write - bytes: " + written );
			}
		}
		
		return( written );
	}
	
	
	//***************************************************************************
	//*
	//* 	Debugging Methods
	//*
	//***************************************************************************/
	
	protected void debugDisplay( String msg )
	{
		if( display != null ) {
			display.display( msg );
		}
	}
	
	
	//***************************************************************************
	//*
	//* 	GPSListenerEntry private class
	//*
	//***************************************************************************/
	
	private class GPSListenerEntry
	{
		// Attributes
		
		private GPSListener			listener		= null;
		private int					types			= GPSEvent.GPS_EVENT_UNDEFINED;
		
		// Constructor
		
		protected GPSListenerEntry( GPSListener listener, int types )
		{
			this.listener 	= listener;
			this.types		= types;
		}
		
		// Accessor Methods
		
		protected GPSListener getListener()
		{
			return( listener );
		}
		
		protected int getTypes()
		{
			return( types );
		}
		
		protected void setTypes( int types )
		{
			this.types = types;
		}
		
	}
}
